package com.code.soulnetty.modbus.client;

import com.code.soulnetty.modbus.EventLoopGroupProvider;
import com.code.soulnetty.modbus.biz.ChannelInfo;
import com.code.soulnetty.modbus.biz.ChannelStatus;
import com.code.soulnetty.modbus.client.channel.AsyncNettyChannelFactory;
import com.code.soulnetty.modbus.client.channel.SyncNettyChannelFactory;
import com.code.soulnetty.modbus.client.codec.ModbusTcpCodec;
import com.code.soulnetty.modbus.client.codec.ModbusTcpDecoder;
import com.code.soulnetty.modbus.client.handler.ModbusClientHandler;
import com.code.soulnetty.modbus.client.service.ProtocolResponseService;
import io.netty.channel.ChannelHandler;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

import static com.code.soulnetty.modbus.biz.Utils.*;

/**
 * modbus 客户端帮助类
 *
 * @author lyf
 * @date 2022-12-16 23:51
 */
@Slf4j
@Component
public class ModbusClientManager {

    //通道异常时间间隔
    public static final int MAX_ERR_OFF_TIME = 30000;

    @Autowired
    private EventLoopGroupProvider provider;

    @Autowired
    private ProtocolResponseService responseService;

    private Map<String, NettyClient> clientMap = new ConcurrentHashMap<>(32);

    public NettyClient client(ChannelInfo channelInfo) {
        String key = channelInfo.key();
        NettyClient nettyClient = clientMap.get(key);
        if (nettyClient != null) {
            return nettyClient;
        }

        nettyClient = clientMap.computeIfAbsent(key, d -> getClient(channelInfo));
        return nettyClient;
    }

    private NettyClient getClient(ChannelInfo channelInfo) {
        if (StringUtils.equals(P_MODBUS_TCP_SYNC, channelInfo.getProtocol())) {
            return createSyncModbusTcpClient(channelInfo, d -> updateChannelStatus(channelInfo, d));
        } else {
            return createModbusTcpClient(channelInfo, d -> updateChannelStatus(channelInfo, d));
        }
    }

    private void updateChannelStatus(ChannelInfo channelInfo, ChannelStatus status) {

        switch (status) {
            case ERR:
                channelInfo.setStatus(channelInfo.getIp() + "_" + status.getName());
                channelInfo.setErrTime(System.currentTimeMillis());
                channelInfo.setTaskStatus(status.name());
                break;
            case OFFLINE:
                channelInfo.setStatus(channelInfo.getIp() + "_" + status.getName());
                channelInfo.setOfflineTime(System.currentTimeMillis());
                channelInfo.setTaskStatus(status.name());
                break;
            case ONLINE:
                if (check(channelInfo)) {
                    channelInfo.setStatus(channelInfo.getIp() + "_" + status.getName());
                    channelInfo.setTaskStatus(status.name());
                }
                break;
            default:
                break;
        }
    }

    /**
     * 判断是否需要更新为通道正常状态
     *
     * @param channelInfo 通道信息
     * @return true 可以，false 不可以
     */
    private boolean check(ChannelInfo channelInfo) {
        return System.currentTimeMillis() - channelInfo.getOfflineTime() > MAX_ERR_OFF_TIME || System.currentTimeMillis() - channelInfo.getErrTime() > MAX_ERR_OFF_TIME;
    }

    public NettyClient createSyncModbusTcpClient(ChannelInfo channelInfo,
                                                 Consumer<ChannelStatus> statusChange){
        int connTimeOut = MapUtils.getInteger(channelInfo.getConfig(), CONN_TIME_OUT, DEFAULT_CONN_TIME_OUT);
        NettyConfig nettyConfig = new NettyConfig(provider.get(P_MODBUS_TCP_SYNC), P_MODBUS_TCP_SYNC, connTimeOut);
        NettyClient modbusTcpClient = new NettyClient(nettyConfig, new SyncNettyChannelFactory(),
                d -> new ChannelHandler[]{
                        new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 4, 2),
                        new ModbusTcpDecoder(new ModbusTcpCodec()),
                        new ModbusClientHandler(channelInfo.getChannelId(), d, statusChange),
                        new ByteArrayEncoder()
                }, responseService);
        return modbusTcpClient;
    }

    public NettyClient createModbusTcpClient(ChannelInfo channelInfo, Consumer<ChannelStatus> statusChange) {
        int connTimeOut = MapUtils.getInteger(channelInfo.getConfig(), CONN_TIME_OUT, DEFAULT_CONN_TIME_OUT);
        NettyConfig nettyConfig = new NettyConfig(provider.get(P_MODBUS_TCP), P_MODBUS_TCP, connTimeOut);
        NettyClient modbusTcpClient = new NettyClient(nettyConfig, new AsyncNettyChannelFactory(),
                d -> new ChannelHandler[]{
                        new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 4, 2),
                        new ModbusTcpDecoder(new ModbusTcpCodec()),
                        new ModbusClientHandler(channelInfo.getChannelId(), d, statusChange),
                        new ByteArrayEncoder()
                }, responseService);
        return modbusTcpClient;
    }
}
